import axios, { AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import { ComponentInternalInstance, getCurrentInstance } from 'vue'
import { PathUtils, StatesUtils as states } from 'cetools'

const request = axios.create({
    timeout: 3000
})

export type Result<T = never> = {
    code: number
    data?: T
    msg: string
}

export type ErrorListener = (error: Error) => void

export interface HostTokenConfig {
    host: string
    tokenKey?: string
}

const hostTokenConfigs = new Map<string, HostTokenConfig>()

export const registerHostTokenConfig = (config: HostTokenConfig): void => {
    if (!hostTokenConfigs.has(config.host)) {
        hostTokenConfigs.set(config.host, config)
    }
}

const errorListenerList = new Map<ComponentInternalInstance, ErrorListener>()

export const watchError = (listener: ErrorListener) => {
    const instance = getCurrentInstance()
    if (instance && !errorListenerList.has(instance)) {
        errorListenerList.set(instance, listener)
    }
}

const doError = (error: Error) => {
    if (errorListenerList.size > 0) {
        errorListenerList.forEach(
            (
                value: ErrorListener,
                key: ComponentInternalInstance,
                map: Map<ComponentInternalInstance, ErrorListener>
            ) => {
                if (!key.isUnmounted) {
                    try {
                        value(error)
                    } catch (e) {
                        console.error(e)
                    }
                } else {
                    map.delete(key)
                }
            }
        )
        return
    }
    console.error(error)
}

request.interceptors.response.use((value: AxiosResponse<never, never>) => {
    if (value.status === 200) {
        if (value.data['code'] && value.data['code'] == 200) {
            return Promise.resolve(value)
        } else if (value.data['msg']) {
            return Promise.reject(new Error(value.data['msg']))
        }
    }
    return value
})

request.interceptors.request.use(
    (value: InternalAxiosRequestConfig<never>): InternalAxiosRequestConfig<never> => {
        if (value.url) {
            const urlInfo = new URL(value.url ? value.url : value.baseURL!)
            const host = urlInfo.host
            let tokenKeyName = 'token'
            const key = host + ':token'
            if (hostTokenConfigs.has(host)) {
                const config = hostTokenConfigs.get(host)
                tokenKeyName = config?.tokenKey ? config.tokenKey : tokenKeyName
            }

            if (states.has(key)) {
                value.headers.set(tokenKeyName, states.getState(key))
            }
        }
        return value
    }
)

export type Success<T = never> = (result: Result<T>) => void
export type Fail = (error: Error) => void

const parameterToUrl = (baseUrl: string, parameter?: never): string => {
    let result = baseUrl.indexOf('?') >= 0 ? baseUrl : baseUrl + '?'
    if (parameter) {
        let paramStr = ''
        for (const key in parameter) {
            paramStr += '&' + key + '=' + parameter[key]
        }
        result = result + PathUtils.formatByChar(paramStr, '&')
    }
    return result
}

class Http {
    post(action: string, data?: never, success?: Success, fail?: Fail): void {
        request
            .post(action, data)
            .then((resp: AxiosResponse) => {
                if (success) {
                    success(resp.data)
                }
            })
            .catch((error: Error) => {
                if (fail) {
                    fail(error)
                } else {
                    doError(error)
                }
            })
    }

    postForm(action: string, data?: FormData, success?: Success, fail?: Fail): void {
        request
            .postForm(action, data)
            .then((resp: AxiosResponse) => {
                if (success) {
                    success(resp.data)
                }
            })
            .catch((error: Error) => {
                if (fail) {
                    fail(error)
                } else {
                    doError(error)
                }
            })
    }

    get(action: string, parameters?: never, success?: Success, fail?: Fail): void {
        request
            .get(parameterToUrl(action, parameters))
            .then((resp: AxiosResponse) => {
                if (success) {
                    success(resp.data)
                }
            })
            .catch((error: Error) => {
                if (fail) {
                    fail(error)
                } else {
                    doError(error)
                }
            })
    }
}

export const http = new Http()
export default request
